Skip to content

Add server.request.body.filenames support for Jetty#10988

Open
jandro996 wants to merge 48 commits intomasterfrom
alejandro.gonzalez/APPSEC-61873-3
Open

Add server.request.body.filenames support for Jetty#10988
jandro996 wants to merge 48 commits intomasterfrom
alejandro.gonzalez/APPSEC-61873-3

Conversation

@jandro996
Copy link
Copy Markdown
Member

@jandro996 jandro996 commented Mar 27, 2026

What Does This Do

Instruments Jetty's multipart request handling to fire both requestFilesFilenames and requestBodyProcessed AppSec gateway events, enabling WAF rules that act on uploaded filenames and multipart form-field values.

Jetty parses multipart bodies through two code paths depending on how the application accesses the request:

  • getParameterMap()extractContentParameters() → internal getParts(MultiMap)
  • Direct getParts() / getPart(String) call from user code

Both paths are instrumented where they exist. Guards on internal state fields (_contentParameters, _multiParts, _multiPartInputStream) prevent double-firing when the same method is called more than once per request.

Module coverage

Module Status Servlet API Coverage Notes
jetty-appsec-7.0 unchanged javax ≤ 8.1.2 getParts() does not exist in Jetty 7.x
jetty-appsec-8.1.3 rewritten javax (3.0) [8.1.3, 9.2.0.RC0) See below
jetty-appsec-9.2 unchanged javax [9.2, 9.3) Already fires requestBodyProcessed; no filename support needed at this range
jetty-appsec-9.3 modified javax [9.3, 9.4.10) Narrowed from [9.3, 12). Added GetFilenamesAdvice + GetFilenamesFromMultiPartAdvice. Double-fire guard uses _multiPartInputStream.
jetty-appsec-9.4 new javax [9.4.10, 11.0) Covers Jetty 9.4.10–10.x. OR-muzzle reference handles _multiParts: MultiParts (9.4.10+, 10.0.10+) and _multiParts: MultiPartFormInputStream (10.0.0–10.0.9). Bytecode discriminator: _dispatcherType: Ljavax/servlet/DispatcherType;.
jetty-appsec-11.0 new jakarta [11.0, 12.0) Jakarta Servlet. Muzzle discriminator: _dispatcherType: Ljakarta/servlet/DispatcherType; in Request.class bytecode — checking classpath presence of jakarta.servlet was unreliable when both javax and jakarta were present simultaneously.

jetty-appsec-8.1.3 details

Replaced brittle ASM bytecode injection (GetPartsMethodVisitor) with clean exit advice:

  • GetFilenamesAdvice on getParts() — fires requestBodyProcessed (form fields) + requestFilesFilenames
  • GetPartAdvice on getPart(String) — same, for single-part uploads that never call getParts(); in Jetty 8.x getPart(String) calls _multiPartInputStream.getPart() directly
  • _multiPartInputStream == null guard prevents re-firing on repeated getParts() calls
  • Classloader matcher uses ASM to inspect Request.class bytecode at agent startup, detecting the getParameters() call inside getParts() that is unique to Jetty 8.x

PartHelper (replacing ParameterCollector):

  • filenameFromPart(): quote-aware Content-Disposition parser; Part.getSubmittedFileName() is Servlet 3.1+ and unavailable here
  • extractFormFields(): reads form-field body values, skips file-upload parts
  • fireBodyProcessedEvent() / fireFilenamesEvent(): encapsulate the full IG callback + blocking-commit logic, returning BlockingException|null for use with @Advice.Thrown(readOnly=false)

Helpers and tests

  • PartHelper (8.1.3) and MultipartHelper (9.3/9.4/11.0) are unit-tested independently in PartHelperTest / MultipartHelperTest
  • Integration tests in jetty-server-7.6, jetty-server-9.3, jetty-server-9.4.21, jetty-server-10.0, jetty-server-11.0 cover both the getParts() and getParameterMap() code paths; a dedicated Jetty10EarlyDepForkedTest covers Jetty 10.0.0–10.0.9

Motivation

Part of APPSEC-61873server.request.body.filenames implementation across server frameworks.

Additional Notes

Bytecode discriminator rationale (9.4 vs 11.0): checking for jakarta.servlet.http.Part on the classpath was unreliable when a Jetty 9.4/10 app ships both javax and jakarta on the classpath (common with Spring Boot 3 migration tooling). The _dispatcherType field descriptor inside Request.class bytecode is unambiguous regardless of classpath contents.

GetFilenamesFromMultiPartAdvice vs GetFilenamesAdvice: in all 9.3/9.4/11.0 modules, extractContentParameters() sets _contentParameters before calling getParts(MultiMap), so the _contentParameters != null guard in GetFilenamesAdvice correctly prevents double-firing when the getParameterMap() path is used.

Part.getSubmittedFileName() availability: Servlet 3.1+ (Jetty 9.1+). For Jetty 8.x (Servlet 3.0), PartHelper.filenameFromPart() parses the Content-Disposition header manually with a quote-aware parser to handle filenames like filename="shell;evil.php" correctly.

Depends on #10973 (merged)

Contributor Checklist

Jira ticket: APPSEC-61873

Note: Once your PR is ready to merge, add it to the merge queue by commenting /merge. /merge -c cancels the queue request. /merge -f --reason "reason" skips all merge queue checks; please use this judiciously, as some checks do not run at the PR-level. For more information, see this doc.

@jandro996 jandro996 added comp: asm waf Application Security Management (WAF) type: enhancement Enhancements and improvements labels Mar 27, 2026
@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented Mar 27, 2026

Benchmarks

Startup

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master alejandro.gonzalez/APPSEC-61873-3
git_commit_date 1776642285 1776952481
git_commit_sha 081af53 af78ea4
release_version 1.62.0-SNAPSHOT~081af53226 1.61.0-SNAPSHOT~af78ea4bbd
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1776954613 1776954613
ci_job_id 1624144082 1624144082
ci_pipeline_id 109304253 109304253
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-rcpqadps 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-rcpqadps 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
module Agent Agent
parent None None

Summary

Found 1 performance improvements and 0 performance regressions! Performance is the same for 59 metrics, 11 unstable metrics.

scenario Δ mean execution_time candidate mean execution_time baseline mean execution_time
scenario:startup:petclinic:profiling:AgentMeter better
[-565.785µs; -234.159µs] or [-6.062%; -2.509%]
8.934ms 9.334ms
Startup time reports for petclinic
gantt
    title petclinic - global startup overhead: candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.067 s) : 0, 1066654
Total [baseline] (11.155 s) : 0, 11154898
Agent [candidate] (1.056 s) : 0, 1055942
Total [candidate] (11.005 s) : 0, 11005205
section appsec
Agent [baseline] (1.247 s) : 0, 1246769
Total [baseline] (11.147 s) : 0, 11147015
Agent [candidate] (1.254 s) : 0, 1254244
Total [candidate] (11.036 s) : 0, 11035826
section iast
Agent [baseline] (1.224 s) : 0, 1224369
Total [baseline] (11.235 s) : 0, 11234507
Agent [candidate] (1.245 s) : 0, 1244520
Total [candidate] (11.29 s) : 0, 11289607
section profiling
Agent [baseline] (1.186 s) : 0, 1186260
Total [baseline] (10.994 s) : 0, 10994154
Agent [candidate] (1.189 s) : 0, 1188736
Total [candidate] (10.969 s) : 0, 10968590
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.067 s -
Agent appsec 1.247 s 180.115 ms (16.9%)
Agent iast 1.224 s 157.715 ms (14.8%)
Agent profiling 1.186 s 119.606 ms (11.2%)
Total tracing 11.155 s -
Total appsec 11.147 s -7.883 ms (-0.1%)
Total iast 11.235 s 79.609 ms (0.7%)
Total profiling 10.994 s -160.744 ms (-1.4%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.056 s -
Agent appsec 1.254 s 198.302 ms (18.8%)
Agent iast 1.245 s 188.578 ms (17.9%)
Agent profiling 1.189 s 132.794 ms (12.6%)
Total tracing 11.005 s -
Total appsec 11.036 s 30.621 ms (0.3%)
Total iast 11.29 s 284.402 ms (2.6%)
Total profiling 10.969 s -36.616 ms (-0.3%)
gantt
    title petclinic - break down per module: candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.236 ms) : 0, 1236
crashtracking [candidate] (1.219 ms) : 0, 1219
BytebuddyAgent [baseline] (638.38 ms) : 0, 638380
BytebuddyAgent [candidate] (632.595 ms) : 0, 632595
AgentMeter [baseline] (30.065 ms) : 0, 30065
AgentMeter [candidate] (29.51 ms) : 0, 29510
GlobalTracer [baseline] (251.849 ms) : 0, 251849
GlobalTracer [candidate] (248.321 ms) : 0, 248321
AppSec [baseline] (32.891 ms) : 0, 32891
AppSec [candidate] (32.403 ms) : 0, 32403
Debugger [baseline] (60.61 ms) : 0, 60610
Debugger [candidate] (59.827 ms) : 0, 59827
Remote Config [baseline] (593.885 µs) : 0, 594
Remote Config [candidate] (593.141 µs) : 0, 593
Telemetry [baseline] (8.084 ms) : 0, 8084
Telemetry [candidate] (9.578 ms) : 0, 9578
Flare Poller [baseline] (6.643 ms) : 0, 6643
Flare Poller [candidate] (5.862 ms) : 0, 5862
section appsec
crashtracking [baseline] (1.232 ms) : 0, 1232
crashtracking [candidate] (1.224 ms) : 0, 1224
BytebuddyAgent [baseline] (660.809 ms) : 0, 660809
BytebuddyAgent [candidate] (667.39 ms) : 0, 667390
AgentMeter [baseline] (12.224 ms) : 0, 12224
AgentMeter [candidate] (12.201 ms) : 0, 12201
GlobalTracer [baseline] (248.196 ms) : 0, 248196
GlobalTracer [candidate] (248.808 ms) : 0, 248808
IAST [baseline] (24.482 ms) : 0, 24482
IAST [candidate] (24.527 ms) : 0, 24527
AppSec [baseline] (184.859 ms) : 0, 184859
AppSec [candidate] (185.003 ms) : 0, 185003
Debugger [baseline] (65.947 ms) : 0, 65947
Debugger [candidate] (65.99 ms) : 0, 65990
Remote Config [baseline] (618.998 µs) : 0, 619
Remote Config [candidate] (625.557 µs) : 0, 626
Telemetry [baseline] (8.522 ms) : 0, 8522
Telemetry [candidate] (8.511 ms) : 0, 8511
Flare Poller [baseline] (3.591 ms) : 0, 3591
Flare Poller [candidate] (3.532 ms) : 0, 3532
section iast
crashtracking [baseline] (1.236 ms) : 0, 1236
crashtracking [candidate] (1.235 ms) : 0, 1235
BytebuddyAgent [baseline] (800.491 ms) : 0, 800491
BytebuddyAgent [candidate] (815.535 ms) : 0, 815535
AgentMeter [baseline] (11.598 ms) : 0, 11598
AgentMeter [candidate] (11.821 ms) : 0, 11821
GlobalTracer [baseline] (239.412 ms) : 0, 239412
GlobalTracer [candidate] (241.987 ms) : 0, 241987
IAST [baseline] (26.397 ms) : 0, 26397
IAST [candidate] (26.209 ms) : 0, 26209
AppSec [baseline] (30.469 ms) : 0, 30469
AppSec [candidate] (32.459 ms) : 0, 32459
Debugger [baseline] (65.141 ms) : 0, 65141
Debugger [candidate] (65.11 ms) : 0, 65110
Remote Config [baseline] (532.88 µs) : 0, 533
Remote Config [candidate] (558.777 µs) : 0, 559
Telemetry [baseline] (9.41 ms) : 0, 9410
Telemetry [candidate] (9.412 ms) : 0, 9412
Flare Poller [baseline] (3.595 ms) : 0, 3595
Flare Poller [candidate] (3.717 ms) : 0, 3717
section profiling
crashtracking [baseline] (1.172 ms) : 0, 1172
crashtracking [candidate] (1.197 ms) : 0, 1197
BytebuddyAgent [baseline] (691.609 ms) : 0, 691609
BytebuddyAgent [candidate] (694.375 ms) : 0, 694375
AgentMeter [baseline] (9.334 ms) : 0, 9334
AgentMeter [candidate] (8.934 ms) : 0, 8934
GlobalTracer [baseline] (208.334 ms) : 0, 208334
GlobalTracer [candidate] (207.782 ms) : 0, 207782
AppSec [baseline] (32.812 ms) : 0, 32812
AppSec [candidate] (32.795 ms) : 0, 32795
Debugger [baseline] (65.671 ms) : 0, 65671
Debugger [candidate] (65.951 ms) : 0, 65951
Remote Config [baseline] (593.967 µs) : 0, 594
Remote Config [candidate] (575.046 µs) : 0, 575
Telemetry [baseline] (7.804 ms) : 0, 7804
Telemetry [candidate] (7.844 ms) : 0, 7844
Flare Poller [baseline] (3.528 ms) : 0, 3528
Flare Poller [candidate] (3.612 ms) : 0, 3612
ProfilingAgent [baseline] (93.869 ms) : 0, 93869
ProfilingAgent [candidate] (94.159 ms) : 0, 94159
Profiling [baseline] (94.417 ms) : 0, 94417
Profiling [candidate] (94.731 ms) : 0, 94731
Loading
Startup time reports for insecure-bank
gantt
    title insecure-bank - global startup overhead: candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.055 s) : 0, 1055472
Total [baseline] (8.794 s) : 0, 8793860
Agent [candidate] (1.06 s) : 0, 1059916
Total [candidate] (8.846 s) : 0, 8845623
section iast
Agent [baseline] (1.23 s) : 0, 1230279
Total [baseline] (9.533 s) : 0, 9533094
Agent [candidate] (1.223 s) : 0, 1223268
Total [candidate] (9.514 s) : 0, 9513576
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.055 s -
Agent iast 1.23 s 174.807 ms (16.6%)
Total tracing 8.794 s -
Total iast 9.533 s 739.234 ms (8.4%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.06 s -
Agent iast 1.223 s 163.352 ms (15.4%)
Total tracing 8.846 s -
Total iast 9.514 s 667.953 ms (7.6%)
gantt
    title insecure-bank - break down per module: candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.225 ms) : 0, 1225
crashtracking [candidate] (1.245 ms) : 0, 1245
BytebuddyAgent [baseline] (631.942 ms) : 0, 631942
BytebuddyAgent [candidate] (637.741 ms) : 0, 637741
AgentMeter [baseline] (29.559 ms) : 0, 29559
AgentMeter [candidate] (29.677 ms) : 0, 29677
GlobalTracer [baseline] (248.188 ms) : 0, 248188
GlobalTracer [candidate] (249.861 ms) : 0, 249861
AppSec [baseline] (32.401 ms) : 0, 32401
AppSec [candidate] (32.795 ms) : 0, 32795
Debugger [baseline] (59.18 ms) : 0, 59180
Debugger [candidate] (59.43 ms) : 0, 59430
Remote Config [baseline] (601.002 µs) : 0, 601
Remote Config [candidate] (597.932 µs) : 0, 598
Telemetry [baseline] (9.549 ms) : 0, 9549
Telemetry [candidate] (8.826 ms) : 0, 8826
Flare Poller [baseline] (6.698 ms) : 0, 6698
Flare Poller [candidate] (3.526 ms) : 0, 3526
section iast
crashtracking [baseline] (1.23 ms) : 0, 1230
crashtracking [candidate] (1.228 ms) : 0, 1228
BytebuddyAgent [baseline] (807.203 ms) : 0, 807203
BytebuddyAgent [candidate] (800.738 ms) : 0, 800738
AgentMeter [baseline] (11.689 ms) : 0, 11689
AgentMeter [candidate] (11.587 ms) : 0, 11587
GlobalTracer [baseline] (239.588 ms) : 0, 239588
GlobalTracer [candidate] (239.04 ms) : 0, 239040
IAST [baseline] (25.759 ms) : 0, 25759
IAST [candidate] (25.746 ms) : 0, 25746
AppSec [baseline] (32.048 ms) : 0, 32048
AppSec [candidate] (32.124 ms) : 0, 32124
Debugger [baseline] (63.32 ms) : 0, 63320
Debugger [candidate] (63.215 ms) : 0, 63215
Remote Config [baseline] (534.759 µs) : 0, 535
Remote Config [candidate] (546.134 µs) : 0, 546
Telemetry [baseline] (9.271 ms) : 0, 9271
Telemetry [candidate] (9.425 ms) : 0, 9425
Flare Poller [baseline] (3.533 ms) : 0, 3533
Flare Poller [candidate] (3.599 ms) : 0, 3599
Loading

Load

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master alejandro.gonzalez/APPSEC-61873-3
git_commit_date 1776642285 1776952481
git_commit_sha 081af53 af78ea4
release_version 1.62.0-SNAPSHOT~081af53226 1.61.0-SNAPSHOT~af78ea4bbd
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1776955014 1776955014
ci_job_id 1624144083 1624144083
ci_pipeline_id 109304253 109304253
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-i367w226 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-i367w226 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

Summary

Found 1 performance improvements and 5 performance regressions! Performance is the same for 13 metrics, 17 unstable metrics.

scenario Δ mean agg_http_req_duration_p50 Δ mean agg_http_req_duration_p95 Δ mean throughput candidate mean agg_http_req_duration_p50 candidate mean agg_http_req_duration_p95 candidate mean throughput baseline mean agg_http_req_duration_p50 baseline mean agg_http_req_duration_p95 baseline mean throughput
scenario:load:insecure-bank:profiling:high_load worse
[+89.141µs; +240.013µs] or [+5.284%; +14.226%]
unstable
[+296.710µs; +1241.685µs] or [+6.234%; +26.089%]
unstable
[-481.194op/s; +0.631op/s] or [-21.924%; +0.029%]
1.852ms 5.529ms 1954.500op/s 1.687ms 4.759ms 2194.781op/s
scenario:load:insecure-bank:iast:high_load better
[-206.236µs; -136.829µs] or [-7.740%; -5.135%]
unsure
[-363.077µs; -41.114µs] or [-4.767%; -0.540%]
unstable
[-60.435op/s; +190.935op/s] or [-4.466%; +14.109%]
2.493ms 7.415ms 1418.531op/s 2.665ms 7.617ms 1353.281op/s
scenario:load:petclinic:code_origins:high_load worse
[+390.904µs; +883.323µs] or [+2.216%; +5.007%]
worse
[+0.578ms; +1.697ms] or [+2.019%; +5.929%]
unstable
[-33.745op/s; +17.370op/s] or [-13.015%; +6.699%]
18.281ms 29.751ms 251.094op/s 17.644ms 28.614ms 259.281op/s
scenario:load:petclinic:profiling:high_load worse
[+1.054ms; +2.052ms] or [+5.900%; +11.483%]
worse
[+0.946ms; +2.560ms] or [+3.228%; +8.735%]
unstable
[-43.015op/s; +7.765op/s] or [-16.768%; +3.027%]
19.420ms 31.061ms 238.906op/s 17.867ms 29.308ms 256.531op/s
Request duration reports for petclinic
gantt
    title petclinic - request duration [CI 0.99] : candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226
    dateFormat X
    axisFormat %s
section baseline
no_agent (18.36 ms) : 18178, 18543
.   : milestone, 18360,
appsec (18.634 ms) : 18446, 18822
.   : milestone, 18634,
code_origins (17.992 ms) : 17815, 18169
.   : milestone, 17992,
iast (17.992 ms) : 17813, 18171
.   : milestone, 17992,
profiling (18.187 ms) : 18005, 18369
.   : milestone, 18187,
tracing (17.965 ms) : 17786, 18144
.   : milestone, 17965,
section candidate
no_agent (18.018 ms) : 17838, 18198
.   : milestone, 18018,
appsec (19.239 ms) : 19046, 19433
.   : milestone, 19239,
code_origins (18.587 ms) : 18401, 18772
.   : milestone, 18587,
iast (18.04 ms) : 17862, 18217
.   : milestone, 18040,
profiling (19.539 ms) : 19340, 19738
.   : milestone, 19539,
tracing (18.014 ms) : 17835, 18193
.   : milestone, 18014,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 18.36 ms [18.178 ms, 18.543 ms] -
appsec 18.634 ms [18.446 ms, 18.822 ms] 274.038 µs (1.5%)
code_origins 17.992 ms [17.815 ms, 18.169 ms] -368.183 µs (-2.0%)
iast 17.992 ms [17.813 ms, 18.171 ms] -368.653 µs (-2.0%)
profiling 18.187 ms [18.005 ms, 18.369 ms] -173.373 µs (-0.9%)
tracing 17.965 ms [17.786 ms, 18.144 ms] -395.574 µs (-2.2%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 18.018 ms [17.838 ms, 18.198 ms] -
appsec 19.239 ms [19.046 ms, 19.433 ms] 1.221 ms (6.8%)
code_origins 18.587 ms [18.401 ms, 18.772 ms] 568.278 µs (3.2%)
iast 18.04 ms [17.862 ms, 18.217 ms] 21.397 µs (0.1%)
profiling 19.539 ms [19.34 ms, 19.738 ms] 1.521 ms (8.4%)
tracing 18.014 ms [17.835 ms, 18.193 ms] -4.476 µs (-0.0%)
Request duration reports for insecure-bank
gantt
    title insecure-bank - request duration [CI 0.99] : candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226
    dateFormat X
    axisFormat %s
section baseline
no_agent (1.277 ms) : 1265, 1289
.   : milestone, 1277,
iast (3.384 ms) : 3340, 3428
.   : milestone, 3384,
iast_FULL (5.984 ms) : 5923, 6045
.   : milestone, 5984,
iast_GLOBAL (3.698 ms) : 3639, 3756
.   : milestone, 3698,
profiling (2.057 ms) : 2040, 2074
.   : milestone, 2057,
tracing (1.894 ms) : 1878, 1910
.   : milestone, 1894,
section candidate
no_agent (1.271 ms) : 1259, 1284
.   : milestone, 1271,
iast (3.224 ms) : 3182, 3267
.   : milestone, 3224,
iast_FULL (6.095 ms) : 6032, 6158
.   : milestone, 6095,
iast_GLOBAL (3.632 ms) : 3569, 3696
.   : milestone, 3632,
profiling (2.32 ms) : 2298, 2342
.   : milestone, 2320,
tracing (1.919 ms) : 1903, 1935
.   : milestone, 1919,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 1.277 ms [1.265 ms, 1.289 ms] -
iast 3.384 ms [3.34 ms, 3.428 ms] 2.107 ms (165.0%)
iast_FULL 5.984 ms [5.923 ms, 6.045 ms] 4.707 ms (368.6%)
iast_GLOBAL 3.698 ms [3.639 ms, 3.756 ms] 2.421 ms (189.6%)
profiling 2.057 ms [2.04 ms, 2.074 ms] 779.854 µs (61.1%)
tracing 1.894 ms [1.878 ms, 1.91 ms] 616.926 µs (48.3%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 1.271 ms [1.259 ms, 1.284 ms] -
iast 3.224 ms [3.182 ms, 3.267 ms] 1.953 ms (153.6%)
iast_FULL 6.095 ms [6.032 ms, 6.158 ms] 4.824 ms (379.4%)
iast_GLOBAL 3.632 ms [3.569 ms, 3.696 ms] 2.361 ms (185.7%)
profiling 2.32 ms [2.298 ms, 2.342 ms] 1.049 ms (82.5%)
tracing 1.919 ms [1.903 ms, 1.935 ms] 647.51 µs (50.9%)

Dacapo

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master alejandro.gonzalez/APPSEC-61873-3
git_commit_date 1776642285 1776952481
git_commit_sha 081af53 af78ea4
release_version 1.62.0-SNAPSHOT~081af53226 1.61.0-SNAPSHOT~af78ea4bbd
See matching parameters
Baseline Candidate
application biojava biojava
ci_job_date 1776954637 1776954637
ci_job_id 1624144086 1624144086
ci_pipeline_id 109304253 109304253
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-fjqz5am5 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-fjqz5am5 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 10 metrics, 2 unstable metrics.

Execution time for biojava
gantt
    title biojava - execution time [CI 0.99] : candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226
    dateFormat X
    axisFormat %s
section baseline
no_agent (15.254 s) : 15254000, 15254000
.   : milestone, 15254000,
appsec (15.305 s) : 15305000, 15305000
.   : milestone, 15305000,
iast (18.353 s) : 18353000, 18353000
.   : milestone, 18353000,
iast_GLOBAL (18.207 s) : 18207000, 18207000
.   : milestone, 18207000,
profiling (15.405 s) : 15405000, 15405000
.   : milestone, 15405000,
tracing (14.79 s) : 14790000, 14790000
.   : milestone, 14790000,
section candidate
no_agent (14.954 s) : 14954000, 14954000
.   : milestone, 14954000,
appsec (14.804 s) : 14804000, 14804000
.   : milestone, 14804000,
iast (18.212 s) : 18212000, 18212000
.   : milestone, 18212000,
iast_GLOBAL (18.074 s) : 18074000, 18074000
.   : milestone, 18074000,
profiling (14.856 s) : 14856000, 14856000
.   : milestone, 14856000,
tracing (14.994 s) : 14994000, 14994000
.   : milestone, 14994000,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 15.254 s [15.254 s, 15.254 s] -
appsec 15.305 s [15.305 s, 15.305 s] 51.0 ms (0.3%)
iast 18.353 s [18.353 s, 18.353 s] 3.099 s (20.3%)
iast_GLOBAL 18.207 s [18.207 s, 18.207 s] 2.953 s (19.4%)
profiling 15.405 s [15.405 s, 15.405 s] 151.0 ms (1.0%)
tracing 14.79 s [14.79 s, 14.79 s] -464.0 ms (-3.0%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 14.954 s [14.954 s, 14.954 s] -
appsec 14.804 s [14.804 s, 14.804 s] -150.0 ms (-1.0%)
iast 18.212 s [18.212 s, 18.212 s] 3.258 s (21.8%)
iast_GLOBAL 18.074 s [18.074 s, 18.074 s] 3.12 s (20.9%)
profiling 14.856 s [14.856 s, 14.856 s] -98.0 ms (-0.7%)
tracing 14.994 s [14.994 s, 14.994 s] 40.0 ms (0.3%)
Execution time for tomcat
gantt
    title tomcat - execution time [CI 0.99] : candidate=1.61.0-SNAPSHOT~af78ea4bbd, baseline=1.62.0-SNAPSHOT~081af53226
    dateFormat X
    axisFormat %s
section baseline
no_agent (1.482 ms) : 1470, 1493
.   : milestone, 1482,
appsec (3.837 ms) : 3614, 4060
.   : milestone, 3837,
iast (2.276 ms) : 2207, 2345
.   : milestone, 2276,
iast_GLOBAL (2.314 ms) : 2244, 2384
.   : milestone, 2314,
profiling (2.117 ms) : 2062, 2173
.   : milestone, 2117,
tracing (2.081 ms) : 2027, 2134
.   : milestone, 2081,
section candidate
no_agent (1.482 ms) : 1471, 1494
.   : milestone, 1482,
appsec (3.855 ms) : 3631, 4078
.   : milestone, 3855,
iast (2.269 ms) : 2199, 2338
.   : milestone, 2269,
iast_GLOBAL (2.319 ms) : 2250, 2389
.   : milestone, 2319,
profiling (2.518 ms) : 2352, 2685
.   : milestone, 2518,
tracing (2.109 ms) : 2054, 2164
.   : milestone, 2109,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.482 ms [1.47 ms, 1.493 ms] -
appsec 3.837 ms [3.614 ms, 4.06 ms] 2.355 ms (158.9%)
iast 2.276 ms [2.207 ms, 2.345 ms] 794.054 µs (53.6%)
iast_GLOBAL 2.314 ms [2.244 ms, 2.384 ms] 832.052 µs (56.1%)
profiling 2.117 ms [2.062 ms, 2.173 ms] 635.364 µs (42.9%)
tracing 2.081 ms [2.027 ms, 2.134 ms] 598.799 µs (40.4%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.482 ms [1.471 ms, 1.494 ms] -
appsec 3.855 ms [3.631 ms, 4.078 ms] 2.373 ms (160.1%)
iast 2.269 ms [2.199 ms, 2.338 ms] 786.561 µs (53.1%)
iast_GLOBAL 2.319 ms [2.25 ms, 2.389 ms] 837.092 µs (56.5%)
profiling 2.518 ms [2.352 ms, 2.685 ms] 1.036 ms (69.9%)
tracing 2.109 ms [2.054 ms, 2.164 ms] 626.637 µs (42.3%)

@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch from e3d4073 to e2d5ed0 Compare April 6, 2026 08:02
Add GetFilenamesAdvice to all three Jetty AppSec modules to collect
uploaded file names from multipart requests and fire the
requestFilesFilenames() IG callback:

- jetty-appsec-8.1.3: intercepts getParts() return value; includes
  Content-Disposition header fallback for Servlet 3.0 (Jetty 9.0)
  where getSubmittedFileName() is not available
- jetty-appsec-9.2: intercepts no-arg getParts() for Servlet 3.1+
- jetty-appsec-9.3: same, applies to Jetty 9.3, 10, 11

Enable testBodyFilenames() in Jetty 9.x, 10 and 11 server tests.
@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch from f2998c3 to 629f074 Compare April 6, 2026 10:50
@jandro996 jandro996 marked this pull request as ready for review April 6, 2026 13:08
@jandro996 jandro996 requested a review from a team as a code owner April 6, 2026 13:08
}
}
// Fallback: parse filename from Content-Disposition header (Servlet 3.0)
if (name == null) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be outside of the main parts loop?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Restructured into two separate loops chosen once before iteration: if getSubmittedFileName != null (Servlet 3.1+) iterate using that method; otherwise iterate parsing the Content-Disposition header (Servlet 3.0 fallback). No per-part branching inside the loop.

transformer.applyAdvice(
named("extractContentParameters").and(takesArguments(0)).or(named("getParts")),
getClass().getName() + "$ExtractContentParametersAdvice");
transformer.applyAdvice(named("getParts"), getClass().getName() + "$GetFilenamesAdvice");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. GetFilenamesAdvice now has a call-depth guard (CallDepthThreadLocalMap with Collection.class) to avoid double-firing when getParts() internally calls getParts(MultiMap)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c732823549

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch 2 times, most recently from ecf65c5 to eae08aa Compare April 6, 2026 14:13
…MultiMap) path

- jetty-appsec-9.3: add call-depth guard (Collection.class) to GetFilenamesAdvice
  to prevent double callback invocation when getParts() calls getParts(MultiMap) internally
- jetty-appsec-9.2: extend GetFilenamesAdvice matcher to all getParts overloads
  (not just no-arg) to cover getParameter*()/getParameterMap() code paths,
  guarded with same call-depth mechanism to avoid double-firing
@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch 2 times, most recently from 3ab9ff7 to 77ec572 Compare April 7, 2026 07:33
@jandro996 jandro996 enabled auto-merge April 7, 2026 08:52
@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch from 2e72584 to d37e03e Compare April 7, 2026 09:23
@jandro996 jandro996 disabled auto-merge April 7, 2026 09:42
@jandro996 jandro996 marked this pull request as draft April 7, 2026 09:48
@jandro996 jandro996 force-pushed the alejandro.gonzalez/APPSEC-61873-3 branch from d37e03e to d8a92f8 Compare April 7, 2026 09:52
- Add BODY_MULTIPART_REPEATED case to TestServlet3 (javax) so Jetty 9.x/10.x
  test modules can exercise the repeated getParts() scenario
- Enable testBodyFilenamesCalledOnce() for Jetty 9.0, 9.0.4, 9.3, 9.4.21, and 10.0
…vice path

- New BODY_MULTIPART_COMBINED endpoint: calls getParameterMap() first (triggers
  GetFilenamesFromMultiPartAdvice via extractContentParameters -> getParts(MultiMap)),
  then getParts() explicitly (GetFilenamesAdvice must not double-fire since
  _contentParameters is already set)
- New test 'file upload filenames called once via parameter map' verifies the
  callback fires exactly once across both advice paths
- Enabled in Jetty 9.0, 9.0.4, 9.3, 9.4.21, 10.0 and 11.0
…eplaces _contentParameters as the getParts() cache

In Jetty 9.3, getParts(MultiMap) sets _contentParameters, so the map==null guard prevents
re-firing on repeated getParts() calls. In Jetty 9.4+, getParts() delegates to getParts(null)
and caches the result in _multiParts instead, leaving _contentParameters null on every call.
Add _multiParts==null as an additional guard (optional=true handles Jetty 9.3 where the
field does not exist).
@jandro996 jandro996 marked this pull request as draft April 20, 2026 08:29
…utStream

- jetty-appsec-9.4: extend muzzle to also cover Jetty 10.0.0–10.0.9, which were
  previously a gap. In those versions _multiParts is typed MultiPartFormInputStream
  (not MultiParts); the primary Reference spec matches 9.4.10+ and 10.0.10+, and an
  OrReference alternative matches 10.0.0–10.0.9. The GetFilenamesAdvice already uses
  typing=DYNAMIC so no advice changes are needed.

- jetty-appsec-8.1.3 PartHelper: wrap part.getInputStream() in try-with-resources to
  avoid leaking file descriptors on file-backed multipart form fields.
@jandro996 jandro996 marked this pull request as ready for review April 20, 2026 08:55
@jandro996
Copy link
Copy Markdown
Member Author

@codex review

@jandro996 jandro996 marked this pull request as draft April 20, 2026 09:01
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f2e2b3998

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Splitting the header on ';' naively truncated filenames that contain
semicolons inside a quoted value, e.g. filename="shell;evil.php" would
produce "shell" instead of the full name. Replace the split() loop with
a quote-aware state-machine parser that skips semicolons inside quoted
strings and handles backslash-escaped characters. Add test cases for
semicolons in filenames, escaped quotes, and filename appearing before
other parameters.
@jandro996
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ce4bf1084

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…c-11.0

Replace the JAKARTA_PART_REFERENCE classpath check with a _dispatcherType
field descriptor check on Request.class bytecode, mirroring the approach
already used by jetty-appsec-9.4. The classpath check passes on any
Jetty 9.4/10 app that has jakarta.servlet-api as a dependency, causing
double-instrumentation of extractContentParameters. The bytecode check is
authoritative: in Jetty 11+ Request.class carries _dispatcherType as
Ljakarta/servlet/DispatcherType;, while 9.4/10 carry the javax descriptor.
@jandro996
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74948a75d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…uploads

In Jetty 8.x, getPart(String name) calls _multiPartInputStream.getPart(String)
directly without delegating to getParts(). Applications that retrieve only one
file via getPart() without ever calling getParts() would have their filename
event missed. Add GetPartAdvice to cover this path.

The charset fix (AI comment 2) was investigated and is not applicable: HTML5
form submissions always use UTF-8 and browsers never include charset= on
individual part Content-Type headers, so the existing hardcoded UTF-8 is correct.
@jandro996
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1508fef49c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…ilename tests in async suite

1. Add _multiPartInputStream == null guard to GetFilenamesAdvice.before() so that
   repeated getParts() calls on the same request (which Jetty caches) do not
   re-fire requestFilesFilenames/requestBodyProcessed WAF callbacks. The field is
   null before the first multipart parse and non-null on all subsequent cached
   calls, matching the pattern used in the 9.4/11.0 advice (_multiParts guard).

2. JettyAsyncHandlerTest already disabled testBodyFilenames() but neglected to
   disable testBodyFilenamesCalledOnce() and testBodyFilenamesCalledOnceCombined(),
   which are now enabled in the Jetty11Test parent. Override both to false in the
   async handler suite to prevent spurious test failures.
@jandro996
Copy link
Copy Markdown
Member Author

@codex review

@jandro996 jandro996 marked this pull request as ready for review April 20, 2026 13:36
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bece33644c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…elper

filenameFromPart() was returning null for both 'no filename parameter' and
'filename=""', causing extractFormFields() to buffer the full body of file
inputs submitted with no file chosen (filename=""). An empty <input type=file>
is still a file part, not a form field. Return "" instead of null so that
callers using != null correctly skip those parts without reading their content.

Update tests to assert "" for empty-filename cases and add regression tests
for extractFormFields/extractFilenames with empty-filename parts.

Note: the second AI comment about getPart(String) double-firing was not
implemented. The bytecode shows the internal call is to
MultiPartInputStream.getParts() (not Request.getParts()), so GetFilenamesAdvice
(which instruments Request.getParts()) is never triggered during a getPart()
call. There is no double-firing.
The jetty-appsec-9.4 early_10_series muzzle pass covers [10.0.0, 10.0.10)
where _multiParts is MultiPartFormInputStream (before the type changed to
MultiParts in 10.0.10). The existing test suite used 10.0.10, leaving the
early versions untested.

Add earlyDep10ForkedTest (Jetty 10.0.9) to exercise the OR-muzzle reference
and verify that multipart filename events fire correctly on the old field type.
All three filename tests pass: filenames, filenames-called-once, filenames-combined.
@jandro996 jandro996 marked this pull request as draft April 23, 2026 07:35
…M API constant

Move duplicated IG-callback + blocking-commit blocks out of advice into
helper methods (MultipartHelper.fireFilenamesEvent / PartHelper.fireBodyProcessedEvent
+ fireFilenamesEvent) that return BlockingException|null, exploiting
@Advice.Thrown(readOnly=false) assignment behaviour.

Also replace hardcoded Opcodes.ASM9 with OpenedClassReader.ASM_API in
RequestGetPartsInstrumentation and fix extractFormFields to use
computeIfAbsent instead of get+put.
@jandro996
Copy link
Copy Markdown
Member Author

@code review

@jandro996 jandro996 requested review from claponcet and smola April 23, 2026 13:06
@jandro996 jandro996 marked this pull request as ready for review April 23, 2026 13:21
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 116d7a270d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…() delegates to getParts()

In Jetty 9.0/9.1 (covered by jetty-appsec-8.1.3's [8.1.3,9.2.0.RC0) range),
Request.getPart(String) delegates to getParts() internally. Without this fix,
GetFilenamesAdvice fires for the full collection (via the internal getParts() call)
and then GetPartAdvice fires again for the returned singleton, because the two
advice classes use independent call-depth keys and cannot see each other.

Fix: peek at the Part.class call depth in GetFilenamesAdvice.before(). If > 1,
GetPartAdvice is active on the stack and will handle the event; skip to avoid
double-firing. In Jetty 8.x (where getPart() does not delegate), the peek is
always 1, so GetFilenamesAdvice continues to fire normally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: asm waf Application Security Management (WAF) type: enhancement Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants